//
//  CTRefreshControl.swift
//  CTRefreshControl
//
//  Created by 王小帅 on 2016/12/6.
//  Copyright © 2016年 王小帅. All rights reserved.
//

import UIKit

/// 下拉刷新的临界点
fileprivate var refreshMaxHeight:CGFloat = 60

/// 下拉刷新的状态
///
/// - Normal: 正常状态
/// - Pulling: 正在拖拽，松开手就刷新
/// - WillRefresh: 已经松开手，即将进入刷新状态
enum XSRefreshStatus {
    case Normal
    case Pulling
    case WillRefresh
}

/// 自定义刷新控件
class CTRefreshControl: UIControl {

    // 是否在刷新
    private var isRefresh:Bool = false
    
    /// 因为该控件适用于UITableView/UICollectionView 所以这里定义一个UIScrollView 作为它们的共同父类
    fileprivate weak var scrollView: UIScrollView?
    
    // 刷新控件
    fileprivate lazy var refreshView:XSRefreshView = XSRefreshView.refreshView()
    
    /// 默认初始化方法
    init(){
        super.init(frame: CGRect())
        setUpUI()
    }
    
    /// 允许用户有XIB创建控件
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        setUpUI()
    }
    
    
    // 是否刷新中
    open var isRefreshing: Bool {
        return  isRefresh
    }
    
    /// 控件即将被加载到父视图
    /// 1.即将加载的时候会调用，此时父视图就是加载控件的视图
    /// 2.退出视图的时候也会调用，此时父视图是nil
    ///
    /// - Parameter newSuperview: 加载控件的视图
    override func willMove(toSuperview newSuperview: UIView?) {
        
        // 可选的父视图 守护下
        guard let sv = newSuperview as? UIScrollView else {
            return
        }
        
        // 赋值属性
        scrollView = sv
        
        // KVO监听属性变化
        // 观察者模式要在不用的时候做移除：
        // -通知：在deinit方法中移除。不移除的话，程序不会崩溃，但是会造成重复注册，重复发送通知，内存泄漏
        // -KVO：第三方框架的下拉刷新都是用它做的，监听contenOffset，在willMove到superview到时候对contenOffset做监听
        // 在removefromsuperview的时候要做移除，否则会崩溃！
        scrollView?.addObserver(self,
                                forKeyPath: "contentOffset",
                                options: [],
                                context: nil)
        
    }
    
    
    /// 移除KVO
    override func removeFromSuperview() {
        
        // 注册的时候是scrollView  这里scrollView是nil 但是它的父视图不是nil 所以用它的父视图来做移除
        superview?.removeObserver(self, forKeyPath: "contentOffset")
        
        super.removeFromSuperview()
    }
    
    
    
    // 所以KVO属性变化都会调用的方法
    override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
        
        
        guard let sv = scrollView else {
            return
        }
        
        // 刷新控件的高度
        let height = -(sv.contentInset.top + sv.contentOffset.y)
        
        // 视图向上滚动的时候，height小于0 不做处理
        if height < 0 {
            return
        }
        
        
        // 注意y的值：当下拉的时候，sv的bounds是被下拉的，所以y的值刚好是和控件的高度相同的
        self.frame = CGRect(x: 0,
                            y: -height,
                            width: sv.bounds.width,
                            height: height)
        
        // 监听下拉临界点，做动画以及文字提示的更改
        if sv.isDragging {
            
            if height > refreshMaxHeight && (refreshView.refreshStatus == XSRefreshStatus.Normal){
                // 更改状态
                refreshView.refreshStatus = .Pulling
                
            }else if height < refreshMaxHeight && (refreshView.refreshStatus == .Pulling){
                // 更改状态
                refreshView.refreshStatus = .Normal
            }
        }else{
            // 如果当前啦过了临界点 就去刷新
            if refreshView.refreshStatus == .Pulling {
                // 把状态更改，此时只有等刷新结束后重新更改了状态 下拉才有效果 避免客户重复刷新
                beginRefreshing()
                
                // 在这里发送事件 供前台监听
                sendActions(for: .valueChanged)
            }
        }

        
    }
    
    /// 开始刷新
    open func beginRefreshing(){
        // 更改本身的状态 对其他无影响 该属性只读
        isRefresh = true
        
        // 守护下 如果当前处于刷新状态就不在刷新了 避免缩紧问题
        guard let sv = scrollView else {
            return
        }
        
        if refreshView.refreshStatus == .WillRefresh {
            return
        }
        // 更改状态
        refreshView.refreshStatus = .WillRefresh
        
        // 让控件停留在界面
        var inset = sv.contentInset
        inset.top += refreshMaxHeight
        
        // 赋值属性
        sv.contentInset = inset
        
        // 不能在这里发事件：正常使用没问题 但是一旦用户手动调用了刷新和结束刷新 会重复刷新
//        sendActions(for: .valueChanged)
    }
    
    
    /// 结束刷新
    open func endRefreshing(){
        // 更改只读属性
        isRefresh = false
        
        // 守护下 如果当前处于刷新状态就不在刷新了 避免缩紧问题
        guard let sv = scrollView else {
            return
        }
        
        if refreshView.refreshStatus != .WillRefresh {
            return
        }
        // 更改状态
        refreshView.refreshStatus = .Normal
        
        // 让控件停留在界面
        var inset = sv.contentInset
        inset.top -= refreshMaxHeight
        
        // 赋值属性
        sv.contentInset = inset
    }

}


// MARK: - 设置UI界面
extension CTRefreshControl {
    
    fileprivate func setUpUI(){
        backgroundColor = superview?.backgroundColor
        
        // 超出边界不显示
//        clipsToBounds = true
        
        // 加入刷新控件
        addSubview(refreshView)

        // 原生自动布局
        refreshView.translatesAutoresizingMaskIntoConstraints = false
        
        // 设置中心对其 这里没有效果 原因是调用自动布局后本来默认的xib宽高会被清零所以要重写设置下宽高
        addConstraint(NSLayoutConstraint(item: refreshView,
                                                     attribute: NSLayoutAttribute.centerX,
                                                     relatedBy: NSLayoutRelation.equal,
                                                     toItem: self,
                                                     attribute: NSLayoutAttribute.centerX,
                                                     multiplier: 1.0,
                                                     constant: 0))
        
        addConstraint(NSLayoutConstraint(item: refreshView,
                                                     attribute: NSLayoutAttribute.bottom,
                                                     relatedBy: NSLayoutRelation.equal,
                                                     toItem: self,
                                                     attribute: NSLayoutAttribute.bottom,
                                                     multiplier: 1.0,
                                                     constant: 0))
        // 重新设置宽高
        addConstraint(NSLayoutConstraint(item: refreshView,
                                                     attribute: .width,
                                                     relatedBy: .equal,
                                                     toItem: nil,
                                                     attribute: .notAnAttribute,
                                                     multiplier: 1.0,
                                                     constant: refreshView.bounds.width))
        
        addConstraint(NSLayoutConstraint(item: refreshView,
                                                     attribute: .height,
                                                     relatedBy: .equal,
                                                     toItem: nil,
                                                     attribute: .notAnAttribute,
                                                     multiplier: 1.0,
                                                     constant: refreshView.bounds.height))
        
    }
}
